十一期间闲来无事,想想好久未写东西了,就总结下最近学习Swift 4.2中新的改变。我们知道Swift 4.2只是Swift 5 ABI稳定版本的一个中间过渡,但这个版本却为我们带来了诸多改进,例如:Optional一致性、更统一的随机数API、遍历enum case、编译器警告等等,上篇博文也提到了“Optional”,那么我们就先从Optional的改变讲起。
IUO到底是什么?
Implicitly Unwrapped Optional,也就是我们常说的IUO,对于这个类型可能有些困惑,在Swift 4.2中,简单来说IUO从一个独立的类型,变成了Optional类型的一个修饰。看好了,我说的是类型修饰。看下面代码:
1 | var a: Int? |
在Swift 4.2之前,a的类型是Optional,而b的类型则是ImplicitlyUnwrappedOptional。而在Swift 4.2之后,a和b的类型,都变成了Optional,只不过,对b来说,编译器会给它做一个标记,在必要的时候自动unwrap它包含的值。也就是说,在4.2版本之后,无论是!还是?,代码中都只有一个optional类型,就是Optional。
尽管Swift官方声明Swift 4.1和4.2是源代码兼容的,但事实上并不完全如此,那么下面总结下这个改动,带来的源代码兼容性相关的变化。
编译器对T!的警告
这类是Swift 4.2之后,我们最常见到的变化。
尽管b的定义是Int!,但从编译器给出的提示中,b实际的定义则是Int?。对于这类问题,绝大多数时候,我们都可以直接把T!变成T?解决。
类型转换引起的问题
由于T!已经不再是个独立的类型,因此,所有对T!的转型行为,都会被定义成废弃操作
编译器会提示我们as T!已经被废弃,会在未来的某个Swift版本中被删除。T!变成了类型修饰之后,转型到T!应该是不被允许的,所有转型到optional的操作都应该明确使用as T?这样的用法。
在Swift 4.2里,任何一个把T!当作类型的用法,理论上都是不允许的:
对于数组来说,我们只能定义包含Array>类型的数组,而不能定义“包含ImplicitlyUnwrappedOptional的数组”。类似的,对于sum来说,我们只能定义,接受两个Optional为参数的函数,而不能定义接受两个“可以自动解值的Optional”作为参数的函数。“自动解值”,只是optional的某种特性,而不是类型的一部分,它不是个类型。
那么什么情况是可以使用T!的呢?是在函数的参数和返回值中,可以使用T!:
1 | func sum(_ m: Int!, _ n: Int!) -> Int! { |
这说明,sum接受两个Optional参数,并返回一个Optional,只不过允许编译器自动解出optional的值而已。但是typealias却不行呢?假设它的定义是合法的,按照规则,它也会被编译器处理成:typealias sum = (Int?, Int?) -> Int?。而我们的目的是要定义一个支持参数和返回值都可以自动解值的函数。这样毫无意义,与其这样费力不讨好,还不如就不允许这样的行为。
不能推导一个不是类型的类型
于此同时我们也不难想到,当IUO不是一个独立的类型之后,所有与之有关的类型自动推导规则也发生了对应的变化。
1 | var x: Int! |
这里,尽管x的定义是Int!,但是无论是y的类型,还是Test(x)的返回值的类型,都是Optional,也就是说,type inference推导不出来Int!这样的的类型,因为它不是一个类型。
IUO对方法匹配的影响
对[T]!使用map方法
1 | let values: [Any]! = [1] |
在Swift 4.2之前的版本里,会先对values的值进行unwrap,得到一个[Any],然后再调用[Any]的map方法对数组中元素进行变换,即使你是否对ImplicitlyUnwrappedOptional这个类型扩展了map方法也是如此。
但到了4.2之后,就不是这样了。values的类型已经变成了Optional<[Any]>,values.map调用的会是Optional.map方法,因此这时的$0的类型就变成了[Any]。把[Any]强制转型成Int,总是失败的,于是,我们会看到这样一条编译器警告
为此,我们有两种处理方法:
第一种,是利用optional chaining,在values非nil的情况下调用map,此时就会调用Array的map方法了。这样,我们会得到一个Optional<[Int]>
1 | _ = values?.map { $0 as! Int } |
第二种,当然就是简单粗暴的直接把optional unwrap出来。这样,我们就会冒着崩溃的风险,直接得到一个[Int]:
1 | _ = values!.map { $0 as! Int } |
nil值桥接方式的改变
在Swift 4.2之前,Swift中的nil桥接到OC中,会导致运行时错误,进而导致程序崩溃。在4.2之后,桥接nil会得到NSNull对象。来看下面这个例子:
1 | class A: NSObject {} |
上面的代码,在Swift 4.2之前,nsArray[0]会因为访问了nil导致运行时错误。但在Swift 4.2之后,我们可以这样:
1 | if let value = element as? NSNull { |
以上,就是有关IUO改动相关的内容。整体上说,这些改动让optional类型的表现更为统一,并且,进一步限制了IUO的应用范围。Swift正在变得越来越好!
在后面博客里,我会介绍Swift 4.2其他有用的改变。